/**
 * 
 */
package gemini.basic.model;

import java.io.Serializable;

/**
 * Parent of all domain object models. Adapted from other class use the
 * same technology: GWT and Hibernate
 *
 */
public abstract class AbstractDomain implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * A special entity ID for transient one (i.e. this entity hasn't been 
     * persisted).
     */
    public static final int TRANSIENT_ENTITY_ID = 0;

    /**
     * Has the hashCode value been leaked while being in transient state? e.g. 
     * hashcode is asking twice on the same object: one when the object is 
     * being in transient state (not saved) and one when it is saved.
     */
    private boolean transientHashCodeLeaked = false;
    
    /**
     * Store the raw entity class name.
     * <p>
     * This hack is necessary to fix a problem of comparing 2 entities yet one 
     * of them is a hibernate proxy. For example, let's compare 2 entities of 
     * type <code>Goods</code>:
     * <ul>
     * <li>the first one is an object. So, the {@link #getClass()} returns "{packagename}.Goods".
     * <li>the second one is a Hibernate proxy. So, the {@link #getClass()} returns
     * "{packagename}.Goods_$$_javaassist...".
     * </ul>
     * Thus, comparison using {@link #getClass()} is not always correct.
     * <p>
     * To solve this problem, we have 2 ways:
     * <ul>
     * <li>use {@link org.hibernate.proxy.HibernateProxyHelper#getClassWithoutInitializingProxy(Object)}: this way is
     * not applicable because entities may be used by GWT in client-side.
     * <li>use a simple field {@link #rawClassName} which contains the raw class name. <b>This solution is chosen!</b>
     * </ul>
     */
    private String rawClassName = getClass().getName();

    /**
     * constructor this entity
     */
    public AbstractDomain() {
        // See documentation for field 'rawClassName' for the reason of this hack.
        int proxySuffixPos = rawClassName.indexOf("_$$");
        if (proxySuffixPos >= 0) {
            rawClassName = rawClassName.substring(0, proxySuffixPos);
        }
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        if (!isPersisted()) { // is new or is in transient state.
            transientHashCodeLeaked = true;
            return super.hashCode();
        } else {
            if (transientHashCodeLeaked) {
                // because hashcode has just been asked for when the object 
                // is in transient state at that time, super.hashCode(); 
                // is returned. Now for consistency, we return the same value.
                return super.hashCode();
            }
            return getId();
        }

        // The above mechanism obey the rule: if 2 objects are equal, 
        // their hashcode must be same.
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        /*
         * The following is a solution that works for hibernate lazy loading 
         * proxies. if (getClass() !=
         * HibernateProxyHelper.getClassWithoutInitializingProxy(obj)) 
         * { return false; }
         */
        if (obj instanceof AbstractDomain) {
            final AbstractDomain other = (AbstractDomain) obj;
            if (isPersisted() && other.isPersisted()) { // both entities are not new

                // because entities are currently used in client-side, we 
                // cannot use HibernateProxyHelper here -> we cannot compare 
                // class for sure they are the same class, just compare ID.
                return getId() == other.getId() && rawClassName.equals(
                        other.getRawClassName());
            }
            // if one of entity is new (transient), they are considered not equal.
        }
        return false;
    }

    /**
     * Checks if this entity has been persisted before.
     * 
     * @return
     */
    public final boolean isPersisted() {
        return getId() > TRANSIENT_ENTITY_ID;
    }

    /**
     * @return primary key of the entity.
     */
    public abstract Integer getId();

    /**
     * @param id
     *            primary key of the entity.
     */
    public abstract void setId(Integer id);

    /**
     * Resets ID back to {@link #TRANSIENT_ENTITY_ID}.
     */
    public final void resetId() {
        setId(TRANSIENT_ENTITY_ID);
    }

    /**
     * Returns the full name of this class.
     * <p>
     * 
     * @return the className.
     */
    public final String getRawClassName() {
        return rawClassName;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(getRawClassName());
        builder.append("[");
        buildStringWithoutBrackets(builder);
        builder.append("]");
        return builder.toString();
    }

    /**
     * Builds string presentation with data of this entity. Notice that the <b>builder</b> argument should be updated
     * and would be used in {@link #toString()}.
     * 
     * @param builder
     */
    protected void buildStringWithoutBrackets(final StringBuilder builder) {
        builder.append("id=").append(getId()).append(", ");
        builder.append("persisted=").append(isPersisted());
    }
}
